/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2021 Universita' degli Studi di Napoli Federico II
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Stefano Avallone <stavallo@unina.it>
 */

#ifndef TUPLE_H
#define TUPLE_H

#include <ns3/attribute-helper.h>
#include <ns3/string.h>

#include <sstream>
#include <type_traits>
#include <utility>
#include <tuple>
#include <algorithm>

namespace ns3 {

/**
 * \brief Stream insertion operator.
 * See https://en.cppreference.com/w/cpp/utility/apply
 *
 * Prints tuple values separated by a comma. E.g., if the tuple contains
 * v1, v2 and v3, then "v1, v2, v3" will be added to the stream.
 *
 * \tparam Args \deduced Tuple arguments
 * \param os the output stream
 * \param t the tuple
 * \returns a reference to the stream
 */
template <class... Args>
std::ostream &
operator << (std::ostream &os, const std::tuple<Args...> &t)
{
  std::apply ([&os](auto&&... args)
              {
                std::size_t n{0};
                ((os << args << (++n != sizeof...(Args) ? ", " : "")), ...);
              },
              t);
  return os;
}


// Doxygen for this class is auto-generated by
// utils/print-introspected-doxygen.h

/**
 * Hold objects of type std::tuple<Args...>.
 * \tparam Args \explicit The list of AttributeValues to be held by this TupleValue
 */
template <class... Args>
class TupleValue : public AttributeValue
{
public:
  /** Type of value stored in the TupleValue. */
  typedef std::tuple<Args...> value_type;
  /** Type returned by Get or passed in Set. */
  typedef std::tuple<std::invoke_result_t<decltype(&Args::Get),Args>...> result_type;

  TupleValue ();

  /**
   * Construct this TupleValue from a std::tuple
   *
   * \param [in] value Value with which to construct.
   */
  TupleValue (const result_type &value);

  Ptr<AttributeValue> Copy (void) const override;
  bool DeserializeFromString (std::string value, Ptr<const AttributeChecker> checker) override;
  std::string SerializeToString (Ptr<const AttributeChecker> checker) const override;

  /**
   * Get the stored values as a std::tuple.
   * 
   * This differs from the actual value stored in the object which is 
   * a tuple of Ptr<AV> where AV is a class derived from AttributeValue.
   * \return stored values as a std::tuple
   */
  result_type Get (void) const;
  /**
   * Set the stored values.
   */
  void Set (const result_type &value);

  /**
   * Get the attribute values as a tuple.
   * \return the attribute values as a tuple
   */
  value_type GetValue (void) const;

  /**
   * Set the given variable to the values stored by this TupleValue object.
   *
   * \tparam T \deduced the type of the given variable (normally, the argument type
   *           of a set method or the type of a data member)
   * \return true if the given variable was set
   */
  template <typename T>
  bool GetAccessor (T &value) const;

private:
  /**
   * Set the attribute values starting from the given values.
   * Used by DeserializeFromString method.
   *
   * \tparam Is \deduced index sequence
   * \param values the given attribute values
   * \return true if the attribute values of this object were set
   */
  template <std::size_t... Is>
  bool SetValueImpl (std::index_sequence<Is...>, const std::vector<Ptr<AttributeValue>>& values);

  value_type m_value;   //!< Tuple of attribute values
};

/**
 * Create a TupleValue object. Enable to write code like this snippet:
 *
 * \code
 * typedef std::tuple<uint16_t, double> Tuple;
 * typedef std::tuple<UintegerValue, DoubleValue> Pack;
 *
 * TupleValue<UintegerValue, DoubleValue> t = MakeTupleValue<Pack> (Tuple {10, 1.5});
 * \endcode
 *
 * \tparam T1 \explicit A std::tuple of the AttributeValue types included in TupleValue
 * \tparam T2 \deduced A std::tuple of the type of elements stored by TupleValue
 * \param t the tuple of elements stored by TupleValue
 * \return a TupleValue object
 */
template <class T1, class T2>
auto MakeTupleValue (T2 t);


/**
 * Checker for attribute values storing tuples.
 */
class TupleChecker : public AttributeChecker
{
public:
  /**
   * Get the checkers for all tuple elements.
   *
   * \return the checkers for all tuple elements
   */
  virtual const std::vector<Ptr<const AttributeChecker>>& GetCheckers (void) const = 0;
};


/**
 * Create a TupleChecker from AttributeCheckers associated with TupleValue elements.
 * 
 * \tparam Args \explicit Attribute value types
 * \tparam Ts \deduced Attribute checker types
 * \param checkers attribute checkers
 * \return Pointer to TupleChecker instance.
 */
template <class... Args, class... Ts>
Ptr<const AttributeChecker> MakeTupleChecker (Ts... checkers);


/**
 * Create an AttributeAccessor for a class data member of type tuple,
 * or a lone class get functor or set method.
 *
 * \tparam Args \explicit Attribute value types
 * \tparam T1 \deduced The type of the class data member,
 *            or the type of the class get functor or set method.
 * \param a1 The address of the data member,
 *           or the get or set method.
 * \return the AttributeAccessor
 */
template <class... Args, class T1>
Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1);

/**
 * Create an AttributeAccessor using a pair of get functor
 * and set methods from a class.
 *
 * \tparam Args \explicit Attribute value types
 * \tparam T1 \deduced The type of the class data member,
 *            or the type of the class get functor or set method.
 * \tparam T2 \deduced The type of the getter class functor method.
 * \param a2 The address of the class method to set the attribute.
 * \param a1 The address of the data member, or the get or set method.
 * \return the AttributeAccessor
 */
template <class... Args, class T1, class T2>
Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1, T2 a2);


}   // namespace ns3

/*****************************************************************************
 * Implementation below
 *****************************************************************************/

namespace ns3 {

template <class... Args>
TupleValue<Args...>::TupleValue ()
  : m_value (std::make_tuple (Args ()...))
{
}

template <class... Args>
TupleValue<Args...>::TupleValue (const result_type &value)
{
  Set (value);
}

template <class... Args>
Ptr<AttributeValue>
TupleValue<Args...>::Copy (void) const
{
  return Create <TupleValue <Args...>> (Get ());
}

template <class... Args>
template <std::size_t... Is>
bool
TupleValue<Args...>::SetValueImpl (std::index_sequence<Is...>, const std::vector<Ptr<AttributeValue>>& values)
{
  auto valueTuple = std::make_tuple (DynamicCast<Args> (values[Is])...);

  bool ok = ((std::get<Is> (valueTuple) != nullptr) && ...);

  if (ok)
    {
      m_value = std::make_tuple (Args (*std::get<Is> (valueTuple))...);
    }
  return ok;
}

template <class... Args>
bool
TupleValue<Args...>::DeserializeFromString (std::string value, Ptr<const AttributeChecker> checker)
{
  auto tupleChecker = DynamicCast<const TupleChecker> (checker);
  if (tupleChecker == nullptr)
    {
      return false;
    }

  auto count = tupleChecker->GetCheckers ().size ();
  if (count != sizeof...(Args))
    {
      return false;
    }

  if (value.empty () || value.front () != '{' || value.back () != '}')
    {
      return false;
    }

  value.erase (value.begin ());
  value.pop_back ();
  std::replace (value.data (), value.data () + value.size (), ',', ' ');

  std::istringstream iss (value);
  std::vector<Ptr<AttributeValue>> values;
  std::size_t i = 0;

  while (iss >> value)
    {
      if (i >= count)
        {
          return false;
        }
      values.push_back (tupleChecker->GetCheckers ().at (i++)->CreateValidValue (StringValue (value)));
      if (values.back () == nullptr)
        {
          return false;
        }
    }

  if (i != count)
    {
      return false;
    }

  return SetValueImpl (std::index_sequence_for<Args...>{}, values);
}

template <class... Args>
std::string
TupleValue<Args...>::SerializeToString (Ptr<const AttributeChecker> checker) const
{
  std::ostringstream oss;
  oss << "{" << Get () << "}";
  return oss.str ();
}

template <class... Args>
typename TupleValue<Args...>::result_type
TupleValue<Args...>::Get (void) const
{
  return std::apply ([](Args... values)
                     {
                       return std::make_tuple (values.Get ()...);
                     },
                     m_value);
}

template <class... Args>
void
TupleValue<Args...>::Set (const typename TupleValue<Args...>::result_type &value)
{
  m_value = std::apply ([](auto&&... args)
                        {
                          return std::make_tuple (Args (args)...);
                        },
                        value);
}

template <class... Args>
typename TupleValue<Args...>::value_type
TupleValue<Args...>::GetValue (void) const
{
  return m_value;
}

template <class... Args>
template <typename T>
bool
TupleValue<Args...>::GetAccessor (T &value) const
{
  value = T (Get ());
  return true;
}




// This internal class defines templated TupleChecker class that is instantiated
// in MakeTupleChecker. The non-templated base ns3::TupleChecker is returned in that
// function. This is the same pattern as ObjectPtrContainer.
namespace internal {

/**
 * Internal checker class templated to each AttributeChecker
 * for each entry in the tuple.
 */
template <class... Args>
class TupleChecker : public ns3::TupleChecker
{
public:
  /**
   * Constructor.
   * \tparam Ts \deduced the type of the attribute checkers
   * \param checkers the attribute checkers for individual elements of the tuple
   */
  template <class... Ts>
  TupleChecker (Ts... checkers)
    : m_checkers {checkers...}
  {
  }
  const std::vector<Ptr<const AttributeChecker>>& GetCheckers (void) const override
  {
    return m_checkers;
  }
  bool Check (const AttributeValue &value) const override
  {
    const TupleValue<Args...>* v = dynamic_cast<const TupleValue<Args...>*> (&value);
    if (v == nullptr)
      {
        return false;
      }
    return std::apply ([this](Args... values)
                       {
                         std::size_t n{0};
                         return (m_checkers[n++]->Check (values) && ...);
                       },
                       v->GetValue ());
  }
  std::string GetValueTypeName (void) const override
  {
    return "ns3::TupleValue";
  }
  bool HasUnderlyingTypeInformation (void) const override
  {
    return false;
  }
  std::string GetUnderlyingTypeInformation (void) const override
  {
    return "";
  }
  Ptr<AttributeValue> Create (void) const override
  {
    return ns3::Create<TupleValue<Args...>> ();
  }
  bool Copy (const AttributeValue &source, AttributeValue &destination) const override
  {
    const TupleValue<Args...> *src = dynamic_cast<const TupleValue<Args...> *> (&source);
    TupleValue<Args...> *dst = dynamic_cast<TupleValue<Args...> *> (&destination);
    if (src == 0 || dst == 0)
      {
        return false;
      }
    *dst = *src;
    return true;
  }

private:
  std::vector<Ptr<const AttributeChecker>> m_checkers;    //!< attribute checkers
};

/**
 * Helper class defining static methods for MakeTupleChecker and MakeTupleAccessor
 * that are called when user specifies the list of AttributeValue types included
 * in a TupleValue type.
 */
template <class... Args>
struct TupleHelper
{
  /**
   * \copydoc ns3::MakeTupleChecker
   */
  template <class... Ts>
  static Ptr<const AttributeChecker> MakeTupleChecker (Ts... checkers)
    {
      return Create <internal::TupleChecker<Args...>> (checkers...);
    }
  /**
   * \copydoc ns3::MakeTupleAccessor(T1)
   */
  template <class T1>
  static Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1)
  {
    return MakeAccessorHelper<TupleValue<Args...>> (a1);
  }
  /**
   * \copydoc ns3::MakeTupleAccessor(T1,T2)
   */
  template <class T1, class T2>
  static Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1, T2 a2)
  {
    return MakeAccessorHelper<TupleValue<Args...>> (a1, a2);
  }
};

/**
 * Helper class defining static methods for MakeTupleValue, MakeTupleChecker and
 * MakeTupleAccessor that are called when user provides a std::tuple of the
 * AttributeValue types included in a TupleValue type.
 * This struct is a partial specialization of struct TupleHelper.
 */
template <class... Args>
struct TupleHelper<std::tuple<Args...>>
{
  /**
   * \copydoc ns3::MakeTupleValue
   */
  static TupleValue<Args...> MakeTupleValue (const typename TupleValue<Args...>::result_type& t)
    {
      return TupleValue<Args...> (t);
    }
  /**
   * \copydoc ns3::MakeTupleChecker
   */
  template <class... Ts>
  static Ptr<const AttributeChecker> MakeTupleChecker (Ts... checkers)
    {
      return Create <internal::TupleChecker<Args...>> (checkers...);
    }
  /**
   * \copydoc ns3::MakeTupleAccessor(T1)
   */
  template <class T1>
  static Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1)
  {
    return MakeAccessorHelper<TupleValue<Args...>> (a1);
  }
  /**
   * \copydoc ns3::MakeTupleAccessor(T1,T2)
   */
  template <class T1, class T2>
  static Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1, T2 a2)
  {
    return MakeAccessorHelper<TupleValue<Args...>> (a1, a2);
  }
};

} // namespace internal



template <class T1, class T2>
auto MakeTupleValue (T2 t)
{
  return internal::TupleHelper<T1>::MakeTupleValue (t);
}

template <class... Args, class... Ts>
Ptr<const AttributeChecker> MakeTupleChecker (Ts... checkers)
{
  return  internal::TupleHelper<Args...>::template MakeTupleChecker<Ts...> (checkers...);
}

template <class... Args, class T1>
Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1)
{
  return internal::TupleHelper<Args...>::template MakeTupleAccessor<T1> (a1);
}

template <class... Args, class T1, class T2>
Ptr<const AttributeAccessor> MakeTupleAccessor (T1 a1, T2 a2)
{
  return internal::TupleHelper<Args...>::template MakeTupleAccessor<T1, T2> (a1, a2);
}



} // namespace ns3

#endif // TUPLE_H
